feat(message): compose editor + Slack-native drafts (#83)#119
Merged
Conversation
nwparker
force-pushed
the
feat/message-compose-native-drafts
branch
2 times, most recently
from
July 18, 2026 06:53
959cb09 to
91a9cd7
Compare
Rename the browser-editor command `message draft` → `message compose`, and add Slack-native drafts under `message draft <list|create|update|delete>`, backed by Slack's undocumented drafts.* session endpoints so drafts show up natively in the user's Slack client. Takes the "compose" direction (option 2 from #83) so the two concepts get distinct names instead of the near-homograph draft/drafts pair. Reuses the drafts.* API layer from #112 (originally by @mikaelq). Hardening in the update path: - refuses drafts it can't faithfully round-trip (scheduled or multi- destination) instead of silently dropping the schedule/recipients; - broadcast is inherited only when the destination is unchanged, so re-addressing never ratchets a reply into a new channel; adds `--no-broadcast` to clear an inherited flag; - rejects `--broadcast` for DM targets, matching `message send`; - `findDraft` prefers a well-formed record if the list response has a malformed duplicate. BREAKING CHANGE: `message draft <target> [text]` no longer opens the editor; use `message compose`. Old usage errors cleanly; `message draft --help` points to compose. No backwards-compat shim. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: GPT-5.6-Sol <noreply@openai.com>
nwparker
force-pushed
the
feat/message-compose-native-drafts
branch
from
July 18, 2026 07:03
91a9cd7 to
cdfbf68
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #83. Supersedes #112 — reuses that PR's Slack
drafts.*API layer, but takes the "compose" direction (option 2) from the issue instead of the pluralmessage draftssubcommand.What & why
The issue flagged that adding Slack-native draft ops under the existing
draftname would confuse an LLM. #112's answer was a near-homograph pair —message draft(browser editor) vsmessage drafts(native) — one letter apart, opposite meanings. This PR instead gives each concept a distinct verb/namespace:message compose <target> [text]— the human-in-the-loop browser editor (renamed frommessage draft; behavior identical). "Compose" matches what it does.message draft <list|create|update|delete>— Slack-native drafts via the undocumenteddrafts.*session endpoints, so drafts show up natively in the user's Slack client (desktop + mobile).createaccepts the usual targets:#channel/name, channel id, user id (opens a DM), or a message URL (drafts a thread reply — same semantics asmessage send).rich_textconversion, so lists/bold/code/mentions render natively.updatereplaces the body but preserves the draft's destination and attached files unless overridden; the conflict-detectionclient_last_updated_tsis auto-fetched forupdate/deletewhen omitted (incl. Slack's 7-digit fractional-padding quirk).later/unreads; noted in the docs.Broadcast semantics (thread replies)
--broadcast("also send to channel") is handled carefully so it can't silently do the wrong thing:update(editing text keeps your broadcast choice), but reset on any--channelre-address so a reply is never ratcheted into a different thread's channel;--no-broadcastflag;message send).Refuses unsafe updates
updatefails loudly instead of corrupting drafts it can't faithfully round-trip: a scheduled draft (would drop the send time) or a multi-destination draft edited text-only (would drop recipients). It points the user to edit in Slack or delete + recreate.message draft <target> [text]no longer opens the editor — usemessage compose. Old usage errors cleanly (error: unknown command '#general'), andmessage draft --helppoints tomessage compose. Deliberate clean break (pre-1.0, v0.9.x); the bundled agent skill is updated in the same PR, so agents pick up the new surface immediately. No magic dispatch / dual-meaningdraft.Structure
src/slack/drafts.ts— API layer (reused from feat(message): Slack-native drafts — message drafts list/create/update/delete #112).src/cli/message-draft-command.ts/message-draft-actions.ts— CLI layer (singulardraftsubcommand group).src/cli/compose-actions.ts— renamed fromdraft-actions.ts.skills/agent-slack/SKILL.md,references/commands.md,references/targets.md,llms.txt.Testing
test/drafts.test.ts(API layer, from feat(message): Slack-native drafts — message drafts list/create/update/delete #112) + newtest/message-draft-actions.test.ts(CLI action layer + a commander-level test locking--broadcast/--no-broadcast/omitted parsing): create, update read-modify-write, scheduled/multi-destination guards, broadcast re-address + DM guards,last_updated_tsauto-fetch/pad,findDraftmalformed-duplicate handling, channel-name hydration.compose/draft/all subcommands, and that oldmessage draft <target>usage errors cleanly.Review
Reviewed by independent correctness + docs sub-agents, then an adversarial loop with GPT-5.6-Sol (xhigh) iterated to convergence. It surfaced and drove fixes for several real issues in the broadcast/thread/DM handling (broadcast one-way ratchets on re-address, DM broadcast via
D…ids/URLs, and error-ordering that reported red-herring lookup failures). A couple of findings were pre-existing repo-wide concerns already owned by other PRs (rendered usergroup/broadcast text → #113) or resolved by rebasing onto currentmain(W-prefixed user IDs → #114).Closes #83.
Credit: Slack
drafts.*API layer originally by @mikaelq in #112.🤖 Reviewed with GPT-5.6-Sol; generated with Claude Code.
Made with Orca 🐋